#define I_(x)   this->args[x]

#include <iostream>

namespace cxxcall
{

bool av_data::call ()
{
  if (this->error ())
    return (false);

  register uintptr_t sp __asm__ ("r13");
  register intptr_t iret2reg __asm__ ("r1");
  ptrdiff_t arg_len = this->curp - this->args;

  sp &= ~7;

  if (arg_len > 0)
    {
      auto frame = (uintptr_t *)__builtin_alloca (arg_len * WSIZE + 16);
      for (ptrdiff_t i = 4; i < arg_len; ++i)
        frame[i - 4] = this->args[i];

      __asm__ __volatile__ ("ldr r1, %0" :: "m" (frame));
    }

#ifdef __armhf__

#  define F_(I)   \
     if (this->farg_mask & (1 << I))   \
       __asm__ __volatile__ ("vldr.32 s" #I ",%0" :: "m" (this->fargs[I]));   \
     else if (this->darg_mask & (1 << I))   \
       __asm__ __volatile__ ("vldr.64 d" #I ",%0" :: "m" (this->dargs[I]))

  F_(0);  F_(1);  F_(2);  F_(3);  F_(4);  F_(5);  F_(6);  F_(7);
  F_(8);  F_(9);  F_(10); F_(11); F_(12); F_(13); F_(14); F_(15);

#endif

#define fct_args   (uintptr_t, uintptr_t, uintptr_t, uintptr_t)

#define call_fct(Type)   \
  ((Type (*) fct_args)this->func) (I_(0), I_(1), I_(2), I_(3))

#define put_ret(Type)   *(Type *)this->raddr = call_fct (Type)

  if (this->rtype == type::float_t)
    put_ret (float);
  else if (this->rtype == type::double_t || this->rtype == type::ldouble_t)
    put_ret (double);
  else
    {
      intptr_t iret = call_fct (intptr_t);
      intptr_t iret2 = iret2reg;

#undef put_ret
#define put_ret(Type)   \
  *(Type *)this->raddr = (Type)iret;   \
  break

      switch (this->rtype)
        {
          case type::void_t:
            break;
          case type::schar_t:
            put_ret (signed char);
          case type::uchar_t:
            put_ret (unsigned char);
          case type::sshort_t:
            put_ret (short);
          case type::ushort_t:
            put_ret (unsigned short);
          case type::sint_t:
          case type::slong_t:
            put_ret (int);
          case type::uint_t:
          case type::ulong_t:
            put_ret (unsigned int);
          case type::sllong_t:
          case type::ullong_t:
            ((intptr_t *)this->raddr)[0] = iret;
            ((intptr_t *)this->raddr)[1] = iret2;
            break;
          case type::struct_t:
            {
              if (!(this->flags & reg_struct_ret))
                break;

              switch (this->rsize)
                {
                  case 1:
                    put_ret (signed char);
                  case 2:
                    put_ret (short);
                  case 4:
                    put_ret (int);
                  case 8:
                    ((intptr_t *)this->raddr)[0] = iret;
                    ((intptr_t *)this->raddr)[1] = iret2;
                    break;
                }
            }
        }
    }

  return (true);
}

} // namespace cxxcall
